﻿
/*
// 初始化列表
# include<iostream>
using namespace std;

class Date
{
public:
	//// 构造函数
	//Date(int year = 1, int month = 1, int day = 1)
	//{
	//	// 函数体内赋值
	//	_year = year;
	//	_month = month;
	//	_day = day;
	//}

	// 初始化列表
	Date(int year = 1, int month = 1, int day = 1)
		:_year(year) // :号 开始， 成员变量用逗号隔开， 每个成员变量后面跟着初始化的值 
		,_month(month)// 注意不能使用 _month = month 这是语法规则
		,_day(day)
	{}

private:
	int _year;
	int _month;
	int _day;
};
*/


/*
// 初始化列表的应用场景
# include<iostream>
using namespace std;

class A
{
public:
	A(int a)
		:_a(a)
	{}
private:
	int _a;
};

class B
{
public:
	B(int a = 10, int ref = 10)
		// 初始化列表是对象的成员变量定义的地方
		// 而这三种成员变量 都必须在定义的时候就要初始化.
		:_aobj(a) // 如果A有默认构造函数，那就可以不用放在初始化列表初始化
		, _ref(ref) 
		, _n(10)
	{
		_x = 10; // 可以放在这里，也可以放到初始化列表中
	}

private:
	// 这三种成员变量 都必须在初始化列表初始化
	// 他们都有个共同点，就是在定义的时候就要初始化。
	A _aobj; // A类没有默认构造函数
	int& _ref; // 引用
	const int _n; // const 

	int _x;  // 这种成员变量就不需要在定义的时候初始化，因此可以不放在初始化列表中初始化
};

int main()
{
	B b;

	return 0;
}
*/

/*
// 尽量使用初始化列表完成初始化
// 因为不管你是否使用初始化列表，对于自定义类型成员变量，一定会先使用初始化列表初始化。
# include<iostream>
using namespace std;

class Time
{
public:
	Time(int hour = 0)
		:_hour(hour)
	{
		cout << "Time()" << endl;
	}
private:
	int _hour;
};

class Date
{
public:
	Date(int day)
	   //:_t;
	{
	   // 如果实在想在函数体内初始化自定义类型，就要这样搞
	   //Time t(1);
	   // _t = t;
	   //但是何必呢，即便这样也要去创建一个变量，也会去调用初始化列表
	}

private:
	int _day;
	Time _t; // 对于自定义类型，会自动调用它的初始化列表完成初始化
};

int main()
{
	Date d(1);
}
*/

/*
//成员变量在类中的声明次序就是其在初始化列表中的 初始化顺序 ，与其在初始化列表中的先后次序无关
# include<iostream>
using namespace std;

class A
{
public:
	A(int a)
		:_a1(a) // 后初始化_a1  a是1 _a1 就是 1
		, _a2(_a1) // _a2先初始化，但是_a1是随机值
	{}

	void Print() {
		cout << _a1 << " " << _a2 << endl;
	}

private:
	int _a2; // _a2先声明 初始化它
	int _a1;
};

int main() 
{
	A aa(1);
	aa.Print(); // 1 -858993460
}

//A.输出1  1
//B.程序崩溃
//C.编译不通过
//D.输出1  随机值

// 最终选 D   
*/


/*
// explicit关键字
// 单参数的隐式类型转换
# include<iostream>
using namespace std;

class Date
{
public:
	explicit Date(int year)
		:_year(year)
	{}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2024); // 构造对象
	Date d2 = d1; // 拷贝构造对象
	Date d3 = 1; 
	// 隐式类型的转换
	// 首先会创建一个Date的临时变量, 构造出一个临时对象tmp(1)，然后在将这个对象拷贝构造给d3(tmp) + 有可能优化成直接构造
	
	// 其实之前我们也接触过隐式类型的转换
	int i = 1;
	double d = i; //首先创建一个double类型的临时变量 去接受1 ，将1 转化成double类型，再赋值给d
	// 如何证明呢？
	//double& d = i; // 编译不通过，因为i会转化成double类的临时变量，临时变量具有常性，只读不能改，这样权限放大了。
	const double& d = i; // 编译通过  权限不在放大

	// 因此对于我们的Date d3 = 1; 也是一样的
	//Date& d3 = 1; // 编译不通过，1会先构造成Date类的临时对象，具有常性，涉及权限放大
	const Date& d3 = 1; // 编译通过, 引用的就是中间产生的临时对象
	// 如果构造函数 加了 explicit关键字的话 这里就无法进行隐式类型转换了

	return 0;
}
*/

/*
// explicit关键字
// 多参数的隐式类型转换 (c++ 11) 
# include<iostream>
using namespace std;

class Date
{
public:
	explicit Date(int year, int month, int day)
		:_year(year)
	{}


	Date(const Date& d)
	{

	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(1, 1, 1);

	// c++ 11 才支持
	//Date d2 = (1, 2, 3); // 这样是错误的语法形式，是不可以的，花括号才可以
	Date d2 = { 1,1,1 }; // 隐式类型转换
	// 如果构造函数 加了 explicit关键字的话 这里就无法进行隐式类型转换了

	return 0;
}
*/


// static成员变量 （静态成员变量）
// 设计出一个类A，可以计算这个类总计生产了多少个对象
// 思路就是，每次创建对象都要调用构造函数，那么通过构造函数去统计生产了多少个对象就行
# include<iostream>
using namespace std;

class A
{
public:
	A()
	{
		++_n;
	}

	A(const A& a) // 拷贝构造
	{
		++_n;
		cout << GetN() << endl; // 非静态成员函数可以调用静态成员函数
		// 突破 (类域 + 访问限定符) 就可以访问静态成员函数
		// 类中是一个整体都在类域中 并且类中不受访问限定符的限制，因此类中可以直接调用静态成员函数
	}

	//int GetN()
	//{
	//	return _n;
	//}

	//const int& GetN()
	//{
	//	return _n;
	//}

	static int GetN() // 没有this指针形参，函数中不能访问非静态的成员函数
	{
	  //_a = 10; // 不允许，不能访问非静态成员
		return _n;
	}

private:
	int _a;
	static int _n; // 这只是个声明, _n位于静态区
	// _n不是属于某个对象的，是属于类的所有对象的，是属于这个类的
};

int A::_n = 0; // 定义

A f(A a)
{
	return a;
}


int main()
{
	A a1; // 1次
	A a2;// 1次

	f(a1); // 生成2次对象
	f(a2);// 2次

	//a1._n = 10; //外界无法修改，因为我们的_n是私有的成员
	// 外界也无法访问我们的_n私有属性
	//cout << A::_n << endl; 
	//cout << a1._n << endl;

	// 我们可以在类中提供接口给外界，让外界能访问_n
	cout << a1.GetN() << endl;
	cout << a2.GetN() << endl;
	// 外界能访问，但是外界是不能修改的。
	//a1.GetN() = 10; // 修改无法成功，因为返回的不是_n本身，而是一个临时变量
	// 除非我们的接口返回值 是 int&  引用
	// 如果我们非要使用引用提升效率，也可以加个const修饰 这样也无法修改
	// 但是实际上这里没必要给引用，本来我们就不想外界修改我们的_n

	// 如果接口的返回值是  static int n 
	cout << A::GetN() << endl; // 此时的接口不属于单个对象，属于整个类
	cout << a1.GetN() << endl;
	cout << a2.GetN() << endl;

	return 0;
}